/*
 * @(#)Object.java   1.0   May 7, 2007
 *
 * Copyright 2000-2007 ETH Zurich. All Rights Reserved.
 *
 * This software is the proprietary information of ETH Zurich.
 * Use is subject to license terms.
 *
 * @(#) $Id: Object.java 428 2007-05-19 23:42:32Z D\aldespin $
 */
package ch.ethz.globis.javaom.core;

import java.util.UUID;

import ch.ethz.globis.javaom.system.Metadata;
import ch.ethz.globis.javaom.utils.AttributeUtils;
import ch.ethz.globis.javaom.utils.BinaryCollectionUtils;
import ch.ethz.globis.javaom.utils.CollectionUtils;
import ch.ethz.globis.javaom.utils.TypeUtils;

/**
 * @author aldespin
 * @version 1.0
 */
public class Object {

   private final UUID objectID;

   public Object(Metadata metadata) {
      this.objectID = UUID.randomUUID();
      // nothing to do for METADATA
   }

   public Object() {
      this.objectID = UUID.randomUUID();
      // dress this with object type TODO does heap overflow
      // this.dress(Metadata.METADATA.getTypeObject(Metadata.OBJECT));
   }

   public String getID() {
      return this.objectID.toString();
   }

   public Bulk getTypes() {
      final Object objectHasTypes = Metadata.METADATA
            .getBinaryCollection(Metadata.OBJECT_HAS_TYPES);
      return BinaryCollectionUtils.range(BinaryCollectionUtils.domainSingleRestriction(
            objectHasTypes, this));
   }

   public void dress(final Object type) {

      // add (this, type) to objectHasTypes
      final Object objectHasTypes = Metadata.METADATA
            .getBinaryCollection(Metadata.OBJECT_HAS_TYPES);

      if (BinaryCollectionUtils.range(
            BinaryCollectionUtils.domainSingleRestriction(objectHasTypes, this)).contains(type)) {
         throw new RuntimeException("object already has type '" + TypeUtils.getName(type) + "'");
      }
      BinaryCollectionUtils.add(objectHasTypes, this, type);

      // retrieve extent collection
      final Object extentCollection = TypeUtils.getExtentCollection(type);
      // add object to extent collection
      CollectionUtils.add(extentCollection, this);
   }

   /**
    * Untyped attribute value setter. The values of all attributes equals to attribute are
    * overriden. Types can choose to have own attribute declaration (multiple equal attribute
    * objects exist) or to inherit from another type. However it must always be the same attribute
    * object.
    * 
    * @param attribute
    * @param value
    */
   @SuppressWarnings("unchecked")
   public void setAttributeValue(final Object attribute, final Object value) {

      // TODO check if object has type that declares the attribute given

      // get objectHasAttributeValues
      final Object objectHasAttributeValues = Metadata.METADATA
            .getBinaryCollection(Metadata.OBJECT_HAS_ATTRIBUTEVALUES);
      // get links from this object to attributevalues
      final Bulk thisHasAttributeValues = BinaryCollectionUtils.domainSingleRestriction(
            objectHasAttributeValues, this);

      if (thisHasAttributeValues.isEmpty()) {
         // there is no attributevalue for this object yet
         Tuple tuple = new Tuple(attribute, value);
         // TODO would be nicer if we could use
         // BinaryCollectionUtils.add(attributevalues, attribute, value)
         Metadata.METADATA.getBinaryCollectionExtent(Metadata.ATTRIBUTEVALUES).add(tuple);
         BinaryCollectionUtils.add(objectHasAttributeValues, this, tuple);
         // done
      } else {
         // there is at least one attributevalue for this object
         final Bulk rangeThisHasAttributeValues = BinaryCollectionUtils
               .range(thisHasAttributeValues);
         final Bulk attributeValues = BinaryCollectionUtils.domainRestriction(
               rangeThisHasAttributeValues, attribute);
         if (attributeValues.isEmpty()) {
            // there is no value for attribute given yet, create new one
            Tuple tuple = new Tuple(attribute, value);
            // TODO would be nicer if we could use
            // BinaryCollectionUtils.add(attributevalues, attribute, value)
            Metadata.METADATA.getBinaryCollectionExtent(Metadata.ATTRIBUTEVALUES).add(tuple);
            BinaryCollectionUtils.add(objectHasAttributeValues, this, tuple);
            // done
         } else {
            // there is at least one value for attribute given, override all of them
            Tuple tuple;
            for (Object current : attributeValues) {
               tuple = (Tuple) current;
               // we recycle tuple and attribute values because they are objects
               if (tuple.range() instanceof AttributeValue) {
                  ((AttributeValue) tuple.range()).setValue(value);
               } else {
                  tuple.setRange(value);
               }
            }
            // done
         }
      }
   }

   public Bulk getAttributeValue(final Object attribute) {

      // TODO check if object has type that declares the attribute given

      // get objectHasAttributeValues
      final Object objectHasAttributeValues = Metadata.METADATA
            .getBinaryCollection(Metadata.OBJECT_HAS_ATTRIBUTEVALUES);
      // get links from this object to attributevalues
      final Bulk thisHasAttributeValues = BinaryCollectionUtils.domainSingleRestriction(
            objectHasAttributeValues, this);

      if (thisHasAttributeValues.isEmpty()) {
         // there is no attributevalue for this object yet
         throw new RuntimeException("this object does not have any attribute values");
      }

      // there is at least one attributevalue for this object
      final Bulk rangeThisHasAttributeValues = BinaryCollectionUtils.range(thisHasAttributeValues);
      final Bulk attributeValues = BinaryCollectionUtils.domainRestriction(
            rangeThisHasAttributeValues, attribute);
      if (attributeValues.isEmpty()) {
         // there is no value for attribute given yet, create new one
         throw new RuntimeException("this object does not have any values for attribute '"
               + AttributeUtils.getName(attribute) + "' of type '"
               + AttributeUtils.getType(attribute) + "' given");
      }

      // there is at least one value for attribute given
      final Bulk result = new Bulk();
      for (Object current : attributeValues) {
         result.add(((Tuple) current).range());
      }
      return result;
   }

   /**
    * Typed attribute value setter. Only the attribute declared by this type is overriden. Types
    * can choose to have own attribute declaration (multiple equal attribute objects exist) or to
    * inherit from another type. However it must always be the same attribute object.
    * 
    * @param attribute
    * @param type
    * @param value
    */
   public void setAttributeValue(final Object attribute, final Object type, final Object value) {
      throw new RuntimeException(
            "Object.setAttributeValue(Object, Object, Object) is not implemented yet");
   }

   public Object getAttributeValue(final Object attribute, final Object type) {
      throw new RuntimeException("Object.getAttributeValue(Object, Object) is not implemented yet");
   }

   @Override
   public int hashCode() {
      return this.objectID.hashCode();
   }

   @Override
   public boolean equals(java.lang.Object obj) {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (getClass() != obj.getClass())
         return false;
      final Object other = (Object) obj;
      if (!this.objectID.equals(other.objectID))
         return false;
      return true;
   }

   @Override
   public String toString() {
      final StringBuffer result = new StringBuffer();
      final String key = Metadata.METADATA.getObjectKey(this);
      if (key != null) {
         result.append("<" + key + " id=\"" + this.objectID.toString() + "\"/>");
      } else {
         result.append("<object" + " id=\"" + this.objectID.toString() + "\"/>");
      }
      return result.toString();
   }
}
